iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
0
Modern Web

從巨人的 Tip 看 Angular系列 第 7

[Day 7] FormGrop、FormControlName 與 FormControl 之間的秘密

  • 分享至 

  • xImage
  •  

昨天的文章帶出了透過提供 NG_VALIDATOR 的方式注入 validator 到 FormControlName 中,阿你有想過 FormControlName 是怎麼對應出 FormControl 實體的嗎?

爬 code 爬起來

第一個線索是 HTML 上的 [formGroup]="formgroup",從這邊可以得知 Angular 是透過這個 directive 與我們在 TypeScript 所建好的 FormGroup 實體建立起關聯。

需要關注的 FormGroupDirective 的實作(完整內容)如下:

@Directive({
  selector: '[formGroup]',
  providers: [formDirectiveProvider],
  host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
  exportAs: 'ngForm'
})
export class FormGroupDirective extends ControlContainer implements Form, OnChanges {
  // ... 略

  /**
   * @description
   * Tracks the `FormGroup` bound to this directive.
   */
  @Input('formGroup') form: FormGroup = null!;

  // ... 略
}

OK,現階段只需要知道 FormGroup 的物件有傳到 FormGroupDirective 就好。

接著就可以來看一下 FormControlName 的實作

@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
export class FormControlName extends NgControl implements OnChanges, OnDestroy {
  private _added = false;
  /**
   * Internal reference to the view model value.
   * @internal
   */
  viewModel: any;

  /**
   * @description
   * Tracks the `FormControl` instance bound to the directive.
   */
  // TODO(issue/24571): remove '!'.
  readonly control!: FormControl;

  /**
   * @description
   * Tracks the name of the `FormControl` bound to the directive. The name corresponds
   * to a key in the parent `FormGroup` or `FormArray`.
   * Accepts a name as a string or a number.
   * The name in the form of a string is useful for individual forms,
   * while the numerical form allows for form controls to be bound
   * to indices when iterating over controls in a `FormArray`.
   */
  // TODO(issue/24571): remove '!'.
  @Input('formControlName') name!: string|number|null;

  // ... 下接 Block 2
}

↑ Block 1

與 FormGroupDirective 一樣,name 會直接被傳入到這個 directive 內,接下來要去哪裡爬我想應該不用我多說,直接往 ngOnChanges 這個方法去就對了:

// ... 略

  /** @nodoc */
  ngOnChanges(changes: SimpleChanges) {
    if (!this._added) this._setUpControl();
    if (isPropertyUpdated(changes, this.viewModel)) {
      _ngModelWarning('formControlName', FormControlName, this, this._ngModelWarningConfig);
      this.viewModel = this.model;
      this.formDirective.updateModel(this, this.model);
    }
  }

// ... 略

  private _checkParentType(): void {
    if (typeof ngDevMode === 'undefined' || ngDevMode) {
      if (!(this._parent instanceof FormGroupName) &&
          this._parent instanceof AbstractFormGroupDirective) {
        ReactiveErrors.ngModelGroupException();
      } else if (
          !(this._parent instanceof FormGroupName) &&
          !(this._parent instanceof FormGroupDirective) &&
          !(this._parent instanceof FormArrayName)) {
        ReactiveErrors.controlParentException();
      }
    }
  }

  private _setUpControl() {
    this._checkParentType();
    (this as {control: FormControl}).control = this.formDirective.addControl(this);
    if (this.control.disabled && this.valueAccessor!.setDisabledState) {
      this.valueAccessor!.setDisabledState!(true);
    }
    this._added = true;
  }

↑ Block 2

因為是 @Input 屬性的變更,直接進到 ngOnChanges 方法也是合情合理,只要 _added 這個變數是 false 的狀態,就會呼叫 _setUpControl 方法。

呼叫 _setUpControl 方法後,首先會先透過呼叫 _checkParentType 這個方法,檢查有沒有 parent,parent 的型別是不是 FormGroup 或是 FormArray,不是的話就拋錯誤出來。

如果沒有錯誤,就可以從 parent 呼叫 addControl 方法,傳入 FormControlName directive,並取回在 parent 物件(FormGroup)定義好的 FormControl 類別物件!

addControl(dir: FormControlName): FormControl {
  const ctrl: any = this.form.get(dir.path);
  setUpControl(ctrl, dir);
  ctrl.updateValueAndValidity({emitEvent: false});
  this.directives.push(dir);
  return ctrl;
}

↑ FormGroupDirective 的 addControl 方法實作內容

以上就是 Angular 從 FormControlName 這一個 directive 從 FormGroupDirective 找出對應的 FormControl 的旅途!讚嘆 DI、讚嘆 Directive!

至於 FormControl 跟 HTML input element 的 value 怎麼互動的,又可以是另一篇文章了呢。

為一同爬天梯的夥伴們加油?

以下按照入團順序列出我們團隊夥伴的系列文章!


上一篇
[Day 6] NG_VALIDATOR,自訂表單驗證器的二三事
下一篇
[Day 8] 所以我說那個 multi 是?
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言